package com.ff.javamonkey.bluetooth;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.UUID;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.ff.javamonkey.test.ui.DeviceList;
import com.ff.javamonkey.test.ui.ScreenTestActivity;
import com.ff.javamonkey.xmodem.XModem;
import com.ff.javamonkey.xmodem.ZModem;

public class BluetoothService {
	// Debugging
	private static final String TAG = "BluetoothTestActivity";
	// Bluetooth File Sending
	private static final String NAME = "BluetoothFileSendingTest";

	private final BluetoothAdapter mAdapter;
	private final Handler mHandler;
	private ConnectThread mConnectThread;
	private ConnectedThread mConnectedThread;
	private AcceptThread mAcceptThread;
	private int mState;
	// Unique UUID for this application? What's unique? What the hell is it?
	private static final UUID MY_UUID = UUID
			.fromString("00001101-0000-1000-8000-00805F9B34FB");

	// Constants that indicate the current connection state
	public static final int STATE_NONE = 0; // we're doing nothing
	public static final int STATE_LISTEN = 1; // now listening for incoming
												// connections
	public static final int STATE_CONNECTING = 2; // now initiating an outgoing
													// connection
	public static final int STATE_CONNECTED = 3; // now connected to a remote
													// device

	public BluetoothService(Context ctx, Handler handler) {
		mAdapter = BluetoothAdapter.getDefaultAdapter();
		mState = STATE_NONE;
		mHandler = handler;
	}

	public synchronized void start() {
		Log.i(TAG, "BluetoothService: start");
		// Cancel any thread attempting to make a connection
		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}
		// Cancel any thread currently running a connection
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}
		// Start the thread to listen to a BluetoothServerSocket
		if (mAcceptThread == null) {
			mAcceptThread = new AcceptThread();
			mAcceptThread.start();
		}
		setState(STATE_LISTEN);
	}

	public synchronized void connect(BluetoothDevice device) {
		Log.i(TAG, "BluetoothService: Conenct");
		if (mState == STATE_CONNECTING) {
			if (mConnectThread != null) {
				mConnectThread.cancel();
				mConnectThread = null;
			}
		}
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}

		mConnectThread = new ConnectThread(device);
		mConnectThread.start();
		setState(STATE_CONNECTING);
	}

	public synchronized void connected(BluetoothSocket socket,
			BluetoothDevice device) {
		Log.i(TAG, "BluetoothService: Connected");
		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}
		if (mAcceptThread != null) {
			mAcceptThread.cancel();
			mAcceptThread = null;
		}
		mConnectedThread = new ConnectedThread(socket);
		mConnectedThread.start();

		Message msg = mHandler.obtainMessage(DeviceList.MESSAGE_DEVICE_NAME);
		Bundle bundle = new Bundle();
		bundle.putString(DeviceList.DEVICE_NAME, device.getName());
		msg.setData(bundle);

		setState(STATE_CONNECTED);
	}

	/**
	 * Set the current state of the chat connection
	 * 
	 * @param state
	 *            An integer defining the current connection state
	 */
	private synchronized void setState(int state) {
		Log.i(TAG, "BluetoothService: setState() " + mState + " -> " + state);
		mState = state;

		// Give the new state to the Handler so the UI Activity can update
		mHandler.obtainMessage(DeviceList.MESSAGE_STATE_CHANGE, state, -1)
				.sendToTarget();
	}

	/**
	 * Return the current connection state.
	 */
	public synchronized int getState() {
		return mState;
	}

	/**
	 * Indicate that the connection attempt failed and notify the UI Activity.
	 */
	private void connectionFailed() {
		setState(STATE_LISTEN);

		// Send a failure message back to the Activity
		Message msg = mHandler.obtainMessage(ScreenTestActivity.MESSAGE_TOAST);
		Bundle bundle = new Bundle();
		bundle.putString(ScreenTestActivity.TOAST, "Unable to connect device");
		msg.setData(bundle);
		mHandler.sendMessage(msg);
	}

	/**
	 * Indicate that the connection was lost and notify the UI Activity.
	 */
	private void connectionLost() {
		setState(STATE_LISTEN);

		// Send a failure message back to the Activity
		Message msg = mHandler.obtainMessage(ScreenTestActivity.MESSAGE_TOAST);
		Bundle bundle = new Bundle();
		bundle.putString(ScreenTestActivity.TOAST, "Device connection was lost");
		msg.setData(bundle);
		mHandler.sendMessage(msg);
	}

	/**
	 * Write file
	 */
	public void writeFile(Object file) {
		ConnectedThread r;
		synchronized (this) {
			// if (mState != STATE_CONNECTED) return;
			r = mConnectedThread;
			if (r == null) {
				try {
					BluetoothServerSocket btServerSocket = mAdapter
							.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
					BluetoothSocket btSocket = btServerSocket.accept();
					connected(btSocket, btSocket.getRemoteDevice());
					r = mConnectedThread;
				} catch (IOException e) {
					e.printStackTrace();
				}

			}
		}
		r.writeFile(file);
	}

	/**
	 * Stop all threads
	 */
	public synchronized void stop() {
		if (mConnectThread != null) {
			mConnectThread.cancel();
			mConnectThread = null;
		}
		if (mConnectedThread != null) {
			mConnectedThread.cancel();
			mConnectedThread = null;
		}
		if (mAcceptThread != null) {
			mAcceptThread.cancel();
			mAcceptThread = null;
		}
		setState(STATE_NONE);
	}

	private class AcceptThread extends Thread {
		// The local server socket
		private final BluetoothServerSocket mmServerSocket;

		public AcceptThread() {
			BluetoothServerSocket tmp = null;
			try {
				tmp = mAdapter
						.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
			} catch (IOException ioe) {
				Log.i(TAG, "BluetoothService: There's an exception?");
				Log.e(TAG, "BluetoothService: listen() failed", ioe);
			}
			mmServerSocket = tmp;
		}

		public void run() {
			Log.i(TAG, "AcceptThread");
			setName("AcceptThread");
			BluetoothSocket socket = null;
			// Listen to the server socket if we're not connected
			Log.i(TAG, "BluetoothService: mmServerSocket is "
					+ (mmServerSocket == null ? "null" : "not null"));
			while (mState != STATE_CONNECTED) {
				Log.i(TAG,
						"AcceptThread->run: BluetoothService: have we ever entering this while?");
				try {
					socket = mmServerSocket.accept();
				} catch (IOException ioe) {
					Log.e(TAG, "BluetoothService: accept() failed", ioe);
					break;
				}
				Log.i(TAG, "BluetoothService: socket is " + (socket == null ? "null" : "not null"));
				// If a connection was accepted
				if (socket != null) {
					Log.i(TAG,
							"BluetoothService: socket is available, and not null");
					synchronized (BluetoothService.this) {
						Log.i(TAG, mState + " State now");
						switch (mState) {
						case STATE_LISTEN:
						case STATE_CONNECTING:
							connected(socket, socket.getRemoteDevice());
							break;
						case STATE_NONE:
						case STATE_CONNECTED:
							try {
								socket.close();
							} catch (IOException ioe) {
								Log.e(TAG,
										"BluetoothService: Could not close unwanted socket",
										ioe);
							}
							break;
						}
					}
				}
			}
			Log.i(TAG, "BluetoothService: AcceptThread - break");
		}

		public void cancel() {
			try {
				mmServerSocket.close();
			} catch (IOException ioe) {
				Log.e(TAG, "BluetoothService: close() of server failed", ioe);
			}
		}
	}

	private class ConnectThread extends Thread {
		private final BluetoothSocket mmSocket;
		private final BluetoothDevice mmDevice;

		public ConnectThread(BluetoothDevice device) {
			mmDevice = device;
			BluetoothSocket tmp = null;
			// Get a BluetoothSocket for a connection with the given
			// BluetoothDevice
			try {
				tmp = device.createRfcommSocketToServiceRecord(MY_UUID);
			} catch (IOException ioe) {
				Log.e(TAG, "BluetoothService: create() failed", ioe);
			}
			mmSocket = tmp;
		}

		public void run() {
			Log.i(TAG, "BluetoothService: Connect Thread YEAHH");
			setName("ConnectThread");
			mAdapter.cancelDiscovery();
			try {
				Log.i(TAG, "BluetoothService: ConnectThread, attempting to connect to mmSocket");
				mmSocket.connect();
			} catch (IOException ioe) {
				Log.e(TAG, "BluetoothService: Connection failed: ", ioe);
				connectionFailed();
				try {
					mmSocket.close();
				} catch (IOException ioe2) {
					Log.e(TAG,
							"BluetoothService: unable to close() socket during connection failure",
							ioe2);
				}
				BluetoothService.this.start();
				return;
			}
			// Reset because we're done
			synchronized (BluetoothService.this) {
				Log.i(TAG, "BluetoothService: If this gets called, then we're on a good track");
				mConnectThread = null;
			}
			// Start the connected thread
			connected(mmSocket, mmDevice);
		}

		public void cancel() {
			try {
				mmSocket.close();
			} catch (IOException ioe) {
				ioe.printStackTrace();
			}
		}
	}

	private class ConnectedThread extends Thread {
		private final BluetoothSocket mmSocket;
		private final InputStream mmInStream;
		private final OutputStream mmOutStream;

		public ConnectedThread(BluetoothSocket socket) {
			mmSocket = socket;
			InputStream tmpIn = null;
			OutputStream tmpOut = null;

			try {
				tmpIn = socket.getInputStream();
				tmpOut = socket.getOutputStream();
			} catch (IOException ioe) {
				Log.e(TAG, "temp sockets not created", ioe);
			}

			mmInStream = tmpIn;
			mmOutStream = tmpOut;
		}

		public void run() {
			byte[] buffer = new byte[1024];
			int bytes;

//			while (true) {
//				try {
//					bytes = mmInStream.read(buffer);
//					mHandler.obtainMessage(ScreenTestActivity.MESSAGE_READ,
//							bytes, -1, buffer).sendToTarget();
//				} catch (IOException ioe) {
//					connectionLost();
//					break;
//				}
//			}
			
			ZModem zmodem = new ZModem("/sdcard/data/883_80.03.MicropreneurManifesto.pdf", mmOutStream, mmInStream); // 500+KB
//			ZModem zmodem = new ZModem("/sdcard/data/1348_NavTech.pdf", mmOutStream, mmInStream); // 2KB
			
//			Log.i(TAG, "We're sending here");
//			byte[] packet = new byte[1024];
//			
//			for (int i = 0; i < packet.length; i++)
//				packet[i] = (byte) (Math.random()*256);
//			
//			int j = 0;
//			for (j = 0; j < 100; j++) {
//				Log.i(TAG, "Looping: "+ j);
//				try {
//					Log.i(TAG, "Sending packet");
//					mmOutStream.write(packet);
//					
//					Log.i(TAG, "Reading Response");
//					int total_size = 0;
//					while (total_size < 1024) {
//						int size = mmInStream.read(buffer, total_size, 1024-total_size);
//						//Log.i(TAG, "Currently read size is " + size);
//						total_size = total_size + size;
//					}
//					
//					//int size = mmInStream.read(buffer);
//					for (int i = 0; i < buffer.length; i++) {
//						if (packet[i] != buffer[i]) {
//							Log.i(TAG, "Iterate number :" + j + " Differs!");
//							break;
//						}
//					}
//					Log.i(TAG, j+".Success!");
//				} catch (IOException e) {
//					e.printStackTrace();
//				}
//			}
		}

		public void write(byte[] buffer) {
			try {
				mmOutStream.write(buffer);
				mHandler.obtainMessage(ScreenTestActivity.MESSAGE_WRITE, -1,
						-1, buffer).sendToTarget();
			} catch (IOException ioe) {
				Log.e(TAG, "Exception during write", ioe);
			}
		}

		public void writeFile(Object file) {
			final File f = new File(file.toString());
			XModem xmodem;
			try {
				xmodem = new XModem(new DataInputStream(
						mmSocket.getInputStream()), new DataOutputStream(
						mmSocket.getOutputStream()),
						new PrintWriter(System.err));
				xmodem.send(file.toString());
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		public void cancel() {
			try {
				mmSocket.close();
			} catch (IOException ioe) {
				Log.e(TAG, "Exception during cancel", ioe);
			}
		}
	}
}
